#include "Line.h"

/********************************************************************************************************
Constructor & Destructor
********************************************************************************************************/

//Default Constructor
Line::Line() : m_shaderColor("Shaders/couleurs.vert", "Shaders/couleurs.frag")
{

	m_shaderColor.initialiser();

}


Line::Line(float* vertex, int nbPoints) : m_shaderColor("Shaders/couleurs.vert", "Shaders/couleurs.frag")
{

	m_shaderColor.initialiser();
	m_lineClose = isLineClose(vertex, nbPoints);
	m_vertex = vertex;
	m_nbPoints = nbPoints;
}


Line::~Line()
{
	delete[] m_vertex;

}


/********************************************************************************************************
Operations
********************************************************************************************************/

//Draw Line
void Line::draw(Matrice &modelview, Matrice &projection)
{
	modelview.push();

	//Shader & Vertex Init
	glUseProgram(m_shaderColor.getProgramID());
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	//Shader Values
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertex);

	glUniformMatrix4fv(glGetUniformLocation(m_shaderColor.getProgramID(), "projection"), 1, GL_TRUE, projection.getValues());
	glUniformMatrix4fv(glGetUniformLocation(m_shaderColor.getProgramID(), "modelview"), 1, GL_TRUE, modelview.getValues());

	glBegin(GL_LINES);
	
	for(int i = 0; i < m_nbPoints -1; i++){
		glVertex3f(m_vertex[(i * 3)], m_vertex[(i * 3)+1], m_vertex[(i * 3)+2]);
		glVertex3f(m_vertex[(i * 3)+3], m_vertex[(i * 3)+4], m_vertex[(i * 3)+5]);
	}
	glEnd();

	//Disable shader and vertex		
	glDisableVertexAttribArray(1);
	glDisableVertexAttribArray(0);
	glUseProgram(0);
	
	modelview.pop();
}


//Draw Points
void Line::drawPoints(Matrice &modelview, Matrice &projection)
{
	modelview.push();

	//Shader & Vertex Init
	glUseProgram(m_shaderColor.getProgramID());
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	//Shader Values
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertex);

	glUniformMatrix4fv(glGetUniformLocation(m_shaderColor.getProgramID(), "projection"), 1, GL_TRUE, projection.getValues());
	glUniformMatrix4fv(glGetUniformLocation(m_shaderColor.getProgramID(), "modelview"), 1, GL_TRUE, modelview.getValues());

	glBegin(GL_POINTS);
	
	for(int i = 0; i < m_nbPoints; i++){
		glVertex3f(m_vertex[(i * 3)], m_vertex[(i * 3)+1], m_vertex[(i * 3)+2]);
	}
	glEnd();

	//Disable shader and vertex		
	glDisableVertexAttribArray(1);
	glDisableVertexAttribArray(0);
	glUseProgram(0);

	modelview.pop();
}


/********************************************************************************************************
Line Tools
********************************************************************************************************/

//Get the middle point for a closed curve
float* Line::getMiddlePoint(){
	float* middle = new float[3];
	middle[0] = 0;
	middle[1] = 0;
	middle[2] = 0;
	
	for(int i = 0; i < m_nbPoints; i++){
		middle[0] += m_vertex[i * 3];
		middle[1] += m_vertex[(i * 3) + 1];
		middle[2] += m_vertex[(i * 3) + 2];
	}

	middle[0] = middle[0] / (float)m_nbPoints;
	middle[1] /= m_nbPoints;
	middle[2] /= m_nbPoints;

	return middle;
}




/********************************************************************************************************
Extrusions Tools
********************************************************************************************************/

//Is first vertex = last vertex
bool Line::isLineClose(float* vertex, int nbPoints){

	//Check if first vertex = last vertex
	if(vertex[0] == vertex[nbPoints * 3 - 3] && vertex[1] == vertex[nbPoints * 3 - 2] && vertex[2] == vertex[nbPoints * 3 - 1])
		return true;

	return false;
}

/********************************************************************************************************
Spline Extrusion
********************************************************************************************************/

//Simple Extrusion
Object3D* Line::simpleExtrusion(int iteration, float distance, float xAxis, float yAxis, float zAxis){
	
	int sizeVertex = m_nbPoints * iteration * 3;

	//Bottom and Top vertex
	if(m_lineClose){
		sizeVertex += 6;
	}

	float *vertex = new float[sizeVertex];
	int nbVertex = 0;

	//Compute all vertex positions
	for(int i = 0; i < iteration; i++){
		for(int j = 0; j < m_nbPoints * 3; j++){
			
			//Compute new vertex positions
			vertex[j + i * m_nbPoints * 3] = m_vertex[j++] + i * distance * xAxis;
			vertex[j + i * m_nbPoints * 3] = m_vertex[j++] + i * distance * yAxis;
			vertex[j + i * m_nbPoints * 3] = m_vertex[j] + i * distance * zAxis;

			nbVertex++;
		}		
	}

	//Bottom & Top
	if(m_lineClose){
		float* bottom = getMiddlePoint();

		//Bottom
		vertex[sizeVertex - 6] = bottom[0];
		vertex[sizeVertex - 5] = bottom[1];
		vertex[sizeVertex - 4] = bottom[2];

		//Top
		vertex[sizeVertex - 3] = bottom[0] + iteration * distance * xAxis;
		vertex[sizeVertex - 2] = bottom[1] + iteration * distance * yAxis;
		vertex[sizeVertex - 1] = bottom[2] + iteration * distance * zAxis;
	}

	
	//Compute Faces index
	std::vector<int> indexFaces = std::vector<int>();
	
	for(int i = 0; i + m_nbPoints < nbVertex -1; i++){
		if ((i +1) % m_nbPoints != 0)
		{
			//First Face
			indexFaces.push_back(i);
			indexFaces.push_back(i + 1);
			indexFaces.push_back(i + m_nbPoints + 1);
			
			//Second Face
			indexFaces.push_back(i);
			indexFaces.push_back(i + m_nbPoints + 1);
			indexFaces.push_back(i + m_nbPoints);			
		}
	}

	//Close Spline
	if(m_lineClose){
		
		//Close Turn
		for(int i = 0; i < iteration - 1; i++)
		{
			//First Face
			indexFaces.push_back((i + 1) * m_nbPoints - 1);
			indexFaces.push_back(i * m_nbPoints);
			indexFaces.push_back((i + 1) * m_nbPoints);

			//Second Face
			indexFaces.push_back((i + 1) * m_nbPoints - 1);
			indexFaces.push_back((i + 1) * m_nbPoints);
			indexFaces.push_back((i+2) * m_nbPoints - 1);		
		}

		//Bottom & Top
		nbVertex += 2;

		for(int i = 0; i < m_nbPoints; i++){
			
			//Bottom
			indexFaces.push_back(i);
			indexFaces.push_back(i +1);
			indexFaces.push_back(nbVertex - 2);
			
			//Top
			indexFaces.push_back(i + (iteration-1) * m_nbPoints);
			indexFaces.push_back(i + (iteration-1) * m_nbPoints +1);
			indexFaces.push_back(nbVertex - 1);
		}
		indexFaces.push_back(m_nbPoints - 1);
		indexFaces.push_back(0);
		indexFaces.push_back(nbVertex - 2);

		indexFaces.push_back(nbVertex - 3);
		indexFaces.push_back((iteration-1) * m_nbPoints);
		indexFaces.push_back(nbVertex - 1);
	}

	unsigned int *index = new unsigned int[indexFaces.size()];
	int numIndex = 0;
	int nb = 0;

	for(unsigned int i = 0; i < indexFaces.size(); i++){
		index[i] = indexFaces[i];			 
	}



	//Create and return object
	return new Object3D(vertex, nbVertex, index, indexFaces.size());
}


//Extrusion by Revolution
Object3D* Line::revolutionExtrusion(int iteration, float xPoint, float yPoint, float zPoint, int xAxis, int yAxis, int zAxis){

	int sizeVertex = m_nbPoints * iteration * 3;

	float *vertex = new float[sizeVertex];
	int nbVertex = 0;

	//Compute all vertex positions
	for(int i = 0; i < iteration; i++){
		for(int j = 0; j < m_nbPoints * 3; j++){
			
			//Compute new vertex positions
			if(yAxis == 1){
				vertex[j + i * m_nbPoints * 3] = xPoint + (m_vertex[j] - xPoint) * cos(i * 2 * PI/iteration);
				vertex[j+1 + i * m_nbPoints * 3] = m_vertex[j+1];
				vertex[j+2 + i * m_nbPoints * 3] = xPoint + (m_vertex[j] - xPoint) * sin(i * 2 * PI/iteration);
			}
			else if(xAxis == 1){
				vertex[j + i * m_nbPoints * 3] = m_vertex[j];
				vertex[j+1 + i * m_nbPoints * 3] = yPoint + (m_vertex[j+1] - yPoint) * sin(i * 2 * PI/iteration);
				vertex[j+2 + i * m_nbPoints * 3] = yPoint + (m_vertex[j+1] - yPoint) * cos(i * 2 * PI/iteration);
			}
			else if(zAxis == 1){
				vertex[j + i * m_nbPoints * 3] = zPoint + (m_vertex[j+2] - zPoint) * sin(i * 2 * PI/iteration);
				vertex[j+1 + i * m_nbPoints * 3] = zPoint + (m_vertex[j+2] - zPoint) * cos(i * 2 * PI/iteration);
				vertex[j+2 + i * m_nbPoints * 3] = m_vertex[j + 2];
			}

			j+=2;
			nbVertex++;
		}		
	}

	
	//Compute Faces index
	std::vector<int> indexFaces = std::vector<int>();
	
	for(int i = 0; i + m_nbPoints < nbVertex -1; i++){
		if ((i +1) % m_nbPoints != 0)
		{
			
			//First Face
			indexFaces.push_back(i);
			indexFaces.push_back(i + 1);
			indexFaces.push_back(i + m_nbPoints + 1);
			
			//Second Face
			indexFaces.push_back(i);
			indexFaces.push_back(i + m_nbPoints + 1);
			indexFaces.push_back(i + m_nbPoints);
		}
	}

	
	//Close Revolution
	for(int i = 0; i < m_nbPoints - 1; i++){
		//First Face
		indexFaces.push_back((iteration -1) * m_nbPoints + i);
		indexFaces.push_back((iteration -1) * m_nbPoints + i + 1);
		indexFaces.push_back(i + 1);

		//Second Face
		indexFaces.push_back((iteration -1) * m_nbPoints + i);
		indexFaces.push_back(i + 1);
		indexFaces.push_back(i);		
	}
	

	unsigned int *index = new unsigned int[indexFaces.size()];
	int numIndex = 0;
	int nb = 0;

	for(unsigned int i = 0; i < indexFaces.size(); i++){
		index[i] = indexFaces[i];			 
	}
	
	//Create and return object
	return new Object3D(vertex, nbVertex, index, indexFaces.size());
}



//Extrusion by a line guide
Object3D* Line::lineFollowExtrusion(Line* guide){

	int sizeVertex = m_nbPoints * guide->m_nbPoints * 3;

	//Bottom and Top vertex
	if(m_lineClose){
		sizeVertex += 6;
	}

	float *vertex = new float[sizeVertex];
	int nbVertex = 0;

	float savePosGuide[3] = {guide->m_vertex[0], guide->m_vertex[1], guide->m_vertex[2]};
	float offsetGuide[3] = {0, 0, 0};

	//Compute all vertex positions
	for(int i = 0; i < guide->m_nbPoints; i++){
		for(int j = 0; j < m_nbPoints * 3; j++){
			
			//Compute new vertex positions
			vertex[j + i * m_nbPoints * 3] = m_vertex[j++] + offsetGuide[0];
			vertex[j + i * m_nbPoints * 3] = m_vertex[j++] + offsetGuide[1];
			vertex[j + i * m_nbPoints * 3] = m_vertex[j] + offsetGuide[2];

			nbVertex++;
		}
		if(i != guide->m_nbPoints -1){
			offsetGuide[0] += guide->m_vertex[(i * 3) + 3] - guide->m_vertex[(i * 3) ];
			offsetGuide[1] += guide->m_vertex[(i * 3) + 4] - guide->m_vertex[(i * 3) + 1];
			offsetGuide[2] += guide->m_vertex[(i * 3) + 5] - guide->m_vertex[(i * 3) + 2];
		}		
	}

	//Bottom & Top
	if(m_lineClose){
		float* bottom = getMiddlePoint();

		//Bottom
		vertex[sizeVertex - 6] = bottom[0];
		vertex[sizeVertex - 5] = bottom[1];
		vertex[sizeVertex - 4] = bottom[2];

		//Top
		vertex[sizeVertex - 3] = bottom[0] + offsetGuide[0];
		vertex[sizeVertex - 2] = bottom[1] + offsetGuide[1];
		vertex[sizeVertex - 1] = bottom[2] + offsetGuide[2];
	}

	
	//Compute Faces index
	std::vector<int> indexFaces = std::vector<int>();
	
	for(int i = 0; i + m_nbPoints < nbVertex -1; i++){
		if ((i +1) % m_nbPoints != 0)
		{
			
			//First Face
			indexFaces.push_back(i);
			indexFaces.push_back(i + 1);
			indexFaces.push_back(i + m_nbPoints + 1);
			
			//Second Face
			indexFaces.push_back(i);
			indexFaces.push_back(i + m_nbPoints + 1);
			indexFaces.push_back(i + m_nbPoints);
		}
	}

	//Close Spline
	if(m_lineClose){
		
		//Close Turn
		for(int i = 0; i < guide->m_nbPoints - 1; i++)
		{
			//First Face
			indexFaces.push_back((i + 1) * m_nbPoints - 1);
			indexFaces.push_back(i * m_nbPoints);
			indexFaces.push_back((i + 1) * m_nbPoints);

			//Second Face
			indexFaces.push_back((i + 1) * m_nbPoints - 1);
			indexFaces.push_back((i + 1) * m_nbPoints);
			indexFaces.push_back((i+2) * m_nbPoints - 1);		
		}

		//Bottom & Top
		nbVertex += 2;

		for(int i = 0; i < m_nbPoints; i++){
			
			//Bottom
			indexFaces.push_back(i);
			indexFaces.push_back(i +1);
			indexFaces.push_back(nbVertex - 2);
			
			//Top
			indexFaces.push_back(i + (guide->m_nbPoints-1) * m_nbPoints);
			indexFaces.push_back(i + (guide->m_nbPoints-1) * m_nbPoints +1);
			indexFaces.push_back(nbVertex - 1);
		}
		indexFaces.push_back(m_nbPoints - 1);
		indexFaces.push_back(0);
		indexFaces.push_back(nbVertex - 2);

		indexFaces.push_back(nbVertex - 3);
		indexFaces.push_back((guide->m_nbPoints-1) * m_nbPoints);
		indexFaces.push_back(nbVertex - 1);
	}
	

	unsigned int *index = new unsigned int[indexFaces.size()];
	int numIndex = 0;
	int nb = 0;

	for(unsigned int i = 0; i < indexFaces.size(); i++){
		index[i] = indexFaces[i];			 
	}


	return new Object3D(vertex, nbVertex, index, indexFaces.size());
}

